#!/usr/bin/python3
# coding:utf-8
import sys
import requests
import json
import time
import threading
import threadpool
import urllib
import yaml

# 读入配置
with open('config.yml') as f:
    config = yaml.load(f)

lock = threading.Lock()
poolSize = config['concurrent']['poolSize']
user = config['user']['account']
passw = config['user']['password']

# 接口相关配置
api = 'https://m.owhat.cn/api?requesttimestap={0}'
ver = '5.5.0'
postData = 'cmd_s={0}&cmd_m={1}&v={2}&client={3}&data={4}'
postAuthData = 'cmd_s={0}&cmd_m={1}&v={2}&client={3}&token={4}&userid={5}&data={6}'
client = urllib.request.quote(config['user']['client'], 'utf-8')
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

# 商品ID
good = config['goods']['id']
# 地址id
addressId = config['user']['addressId']
# 购买数量
buyNum = config['goods']['buyNum']
# 购买档次 180 380 480 680 880 1080 1280
priceLevel = config['goods']['priceLevel']
# 答案列表
answers = []

# 全局变量
success = False
cookies = None
authContext = None
priceId = 0
# 获取的问题
questions = []
# 组合后的问题
extAnswer = []
#订单数据
orderData = None
#支付的订单号
orderId = ''

class LoginContext:
    'save token context'
    toekn = None
    cookies = None
    userId = None


def getApiUrl():
    '获取调用接口地址'
    millis = int(round(time.time() * 1000))
    return api.format(millis)


def execApi(cmd_s, cmd_m, data):
    '通用API调用方法'
    global ver, client, cookies
    url = getApiUrl()
    print('执行API {0} {1}'.format(cmd_s, cmd_m))
    d = postData.format(cmd_s, cmd_m, ver, client, data)
    res = requests.post(url=url, data=d, headers=headers, cookies=cookies)
    if res.status_code != 200:
        print('重试API {0} {1}'.format(cmd_s, cmd_m))
        return execApi(cmd_s, cmd_m, data)

    result = json.loads(res.text)
    if result['result'] != 'success':
        print('请求API报错, 重试API {0} {1}'.format(cmd_s, cmd_m))
        return execApi(cmd_s, cmd_m, data)

    print('执行API结果 {0} {1} {2}'.format(cmd_s, cmd_m, result))
    return result


def execAuthApi(cmd_s, cmd_m, data, context):
    '执行登录后API调用方法'
    global ver, client, cookies, authContext, success
    url = getApiUrl()
    print('执行权限API {0} {1}'.format(cmd_s, cmd_m))
    d = postAuthData.format(cmd_s, cmd_m, ver, client, context.token,
                            context.userId, data)
    res = requests.post(url=url, data=d, headers=headers, cookies=cookies)
    # print(res.text)
    cookies = res.cookies.get_dict()
    if res.status_code != 200:
        print('重试权限API {0} {1}'.format(cmd_s, cmd_m))
        return execAuthApi(cmd_s, cmd_m, data, context)

    result = json.loads(res.text)
    if result['result'] != 'success':
        # 为下订单提前处理的
        if result['message'] == '您有一个订单正在下单中,请稍后再试':
            success = True
            print(
                '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!下单完毕了 {0}'.format(
                    result))
            print('下单完成，线程退出啦')
            return result

        # 登录过期
        if 'code' in result and result['code'] == 'e_sys_invalid_token':
            # TODO 加锁
            print('!登录过期，重新登录')
            authContext = login(user, passw)
            return execAuthApi(cmd_s, cmd_m, data, authContext)

        # 超过购买数
        if 'code' in result and result['code'] == 'shop_order_submit_goods_limit_error':
            success = True
            print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!下单完毕了 {0}'.format(result))
            print('下单完成，线程退出啦')
            return result

        print('请求API报错, 重试API {0} {1} {2}'.format(cmd_s, cmd_m, result))
        return execAuthApi(cmd_s, cmd_m, data, context)

    print('执行API结果 {0} {1} {2}'.format(cmd_s, cmd_m, res.text))

    return result


def getVerifyCode():
    '获取登录验证码'
    cmd_s = 'base.verifycode'
    cmd_m = 'getimagecodeurl'
    data = urllib.request.quote('{"btype":"login"}', 'utf-8')

    result = execApi(cmd_s, cmd_m, data)
    try:
        codekey = result['data']['imagecodekey']
    except BaseException:
        print('重试getVerifyCode')
        return getVerifyCode()

    print('imagecodekey: {0}'.format(codekey))

    return codekey;


def login(account, password):
    '登录'
    codekey = getVerifyCode()
    cmd_s = 'user.account'
    cmd_m = 'login'
    data = {
        "account": account,
        "password": password,
        "imagecodekey": codekey
    }

    dataStr = urllib.request.quote(json.dumps(data), 'utf-8')
    result = execApi(cmd_s, cmd_m, dataStr)
    try:
        context = LoginContext()
        context.token = result['data']['token']
        context.userId = result['data']['userid']
    except BaseException:
        print('重试login')
        return login(account, password)

    print('!important 登录成功 id: {0} token: {1}'.format(context.userId, context.token))

    return context


def findPriceAndStoke(goodsId, context):
    '获取商品信息'
    global priceId, priceLevel, questions
    cmd_s = 'shop.price'
    cmd_m = 'findPricesAndStock'
    data = {"fk_goods_id": goodsId}

    dataStr = urllib.request.quote(json.dumps(data), 'utf-8')
    result = execAuthApi(cmd_s, cmd_m, dataStr, context)
    # print('场次票价信息:{0}'.format(result))
    try:
        print('!商品价格表!!!!!!important!!!!!{0}'.format(result['data']['prices']))
        priceId = result['data']['prices'][priceLevel]['id']
        price = result['data']['prices'][priceLevel]['price']
        questions = result['data']['extlist']
        print('!important获取到目标商品价格id: {0} 价格:{1}'.format(priceId, price))
        print('!important获取到目标商品问题: {0}', questions)
    except BaseException:
        print('重试获取商品信息{0}'.format(goodsId))
        return findPriceAndStoke(goodsId, context)


def getOrder(taskId):
    '下单'
    global good, buyNum, priceId, authContext, extAnswer, success, goodsId
    if success:
        print('下单成功，线程退出{0}'.format(taskId))
        return

    cmd_s = 'shop.order'
    cmd_m = 'submitOrder'
    print('当前执行task:{0} goodId:{1} priceId:{2} addressId:{3} buyNum:{4}'.format(
        taskId, good, priceId, addressId, buyNum))
    result = execAuthApi(cmd_s, cmd_m, orderData, authContext)
    if success:
        # 表明订单提前完成下单 直接退出
        return;
    print('下订单: {0}'.format(result))

    try:
        if result['result'] == 'success':
            success = True
            orderId = result['data']['ordernolist'][0]
            print('!!!!!!important !!!!!!!!!!!!!!下单完毕，订单编号 {0}'.format(orderId))
            print('下单完成，线程退出啦{0}'.format(taskId))
            return
        elif result['code'] == 'shop_order_submit_over_max_times':
            success = True
            print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!下单完毕了{0}'.format(result))
            print('下单完成，线程退出啦{0}'.format(taskId))
            return
        else:
            print(
                '下单失败了！！！！！！！！！！task: {0} msg: {1}'.format(taskId, result['message']))
            # return getOrder(taskId)

    except BaseException:
        print('解析错误，重新下单: {0}'.format(goodsId))
        # return getOrder(taskId)


class Logger(object):
    def __init__(self, filename="def.log"):
        self.terminal = sys.stdout
        self.log = open(filename, 'a')

    def write(self, message):
        t = message
        if '\n' != t:
            t = '{} - {}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), t)

        lock.acquire()
        self.terminal.write(t)
        self.log.write(t)
        lock.release()

    def flush(self):
        pass


def start():
    task = []
    for num in range(0, poolSize):
        task.append(num)

    # start packing task
    requests = threadpool.makeRequests(doTask, task)
    [pool.putRequest(req) for req in requests]
    pool.wait()

    print('Finish task ')


def doTask(taskId):
    # 不再递归 防止服务挂掉的时候无线递归 栈溢出
    while (success != True):
        getOrder(taskId)


def findInAnswer(key):
    global answers
    for i in range(0, len(answers)):
        if (answers[i]['key'] == key):
            return i;

    return -1


def assertQuestion():
    '匹配问题设置答案'
    global questions, answers, extAnswer
    print('开始匹配答案')
    for question in questions:
        key = question['key']
        index = findInAnswer(key)
        if index != -1:
            extAnswer.append(answers[index])
        else:
            # 不能输入空
            answer = ''
            while answer == '':
                print('请输入问题答案:{0}'.format(key))
                answer = input()

            ansObj = {
                "key": key,
                "value": answer,
                "required": question['required']
            }
            extAnswer.append(ansObj)

    print('匹配答案完毕:{0}'.format(extAnswer))


def loadData():
    '读取已经请求配置的数据'
    global answers, authContext, priceId, questions
    # 读取问题配置 需要从接口返回的数据保存
    if 'questions' in config['user'] and config['user']['questions']:
        questions = config['user']['questions']
    else:
        questions = []

    # 读取已配置的问题答案
    answers = config['user']['answers']
    # 读取登录信息
    authContext = LoginContext()
    authContext.token = config['user']['token']
    authContext.userId = config['user']['userId']
    # 读取购买物品数据
    priceId = config['goods']['priceId']


def preparedOrder():
    '准备订单数据'
    global addressId, good, buyNum, priceId, extAnswer, orderData
    data = {
        "addressId": addressId,
        "isshopagreement": 1,
        "goodslist": [
            {
                "goodsid": good,
                "goodspricecatelist": [
                    {
                        "goodspricetypeid": priceId,
                        "num": buyNum
                    }
                ],
                "extrainfo": extAnswer
            }
        ]
    }

    orderData = urllib.request.quote(json.dumps(data), 'utf-8')

def pay():
    '支付宝下订单，他们微信好像不支持web支付'
    global orderId, authContext
    cmd_s = 'shop.pay'
    cmd_m = 'orderpay'
    data = {"paytype":"1","ordernolist":[orderId]}
    dataStr = urllib.request.quote(json.dumps(data), 'utf-8')
    result = execAuthApi(cmd_s, cmd_m, dataStr, authContext)
    print('请求支付数据结果{0}'.format(result))


if __name__ == '__main__':
    # 重定向输出日志
    sys.stdout = Logger("tickets-logs.txt")
    # 初始化任务线程池
    pool = threadpool.ThreadPool(poolSize)
    # 读取配置中预置数据
    loadData()
    # 设定请求上下文
    if authContext is None:
        authContext = login(user, passw)
    else:
        print('已经存在登录数据，直接登录{0}'.format(authContext))

    # 获取票价、问题信息
    if int(priceId) == 0 or len(questions) == 0:
        findPriceAndStoke(good, authContext)
    else:
        print("已载入问题{0}和目标品类Id{1}".format(questions, priceId))
    # 匹配问题
    assertQuestion()
    # 准备订单数据
    preparedOrder()
    # 多线程下单
    start()
    #orderId = '10000000320801232222'
    #支付
    # pay()
    # getOrder(0)
